/**
 * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/runtime/ecma_class_linker_extension.h"
#include "plugins/ecmascript/runtime/js_tagged_value-inl.h"
#include "plugins/ecmascript/runtime/mem/mem_manager-inl.h"
#include "plugins/ecmascript/runtime/ecma_string.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "runtime/include/class_linker-inl.h"
#include "runtime/include/coretypes/class.h"

namespace ark::ecmascript {
using SourceLang = panda_file::SourceLang;

bool EcmaClassLinkerExtension::InitializeImpl([[maybe_unused]] bool cmpStrEnabled)
{
    return true;
}

EcmaClassLinkerExtension::~EcmaClassLinkerExtension()
{
    if (!IsInitialized()) {
        return;
    }
    FreeLoadedClasses();
}

void EcmaClassLinkerExtension::InitClasses(EcmaVM *vm)
{
    ASSERT(IsInitialized());
    vm_ = vm;
    LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(GetLanguage());
    [[maybe_unused]] EcmaHandleScope scope(vm->GetJSThread());

    auto *classClass = NewClass(ctx.GetClassClassDescriptor(), 0, 0, GetClassSize(ClassRoot::CLASS));
    if (classClass == nullptr) {
        return;
    }
    classClass->SetObjectSize(ObjectHeader::ObjectHeaderSize());
    classClass->SetState(Class::State::LOADED);
    Runtime::GetCurrent()->GetClassLinker()->AddClassRoot(ClassRoot::CLASS, classClass);

    auto *objClass = NewClass(ctx.GetObjectClassDescriptor(), 0, 0, GetClassSize(ClassRoot::OBJECT));
    if (objClass == nullptr) {
        return;
    }
    objClass->SetObjectSize(TaggedObject::TaggedObjectSize());
    objClass->SetState(Class::State::LOADED);
    Runtime::GetCurrent()->GetClassLinker()->AddClassRoot(ClassRoot::OBJECT, objClass);
}

ClassLinkerContext *EcmaClassLinkerExtension::CreateApplicationClassLinkerContext(const PandaVector<PandaString> &path)
{
    PandaVector<PandaFilePtr> appFiles;
    appFiles.reserve(path.size());
    for (auto &p : path) {
        auto pf = panda_file::OpenPandaFileOrZip(p, panda_file::File::READ_WRITE);
        if (pf == nullptr) {
            return nullptr;
        }
        appFiles.push_back(std::move(pf));
    }
    return ClassLinkerExtension::CreateApplicationClassLinkerContext(std::move(appFiles));
}

Class *EcmaClassLinkerExtension::NewClass(const uint8_t *descriptor, size_t vtableSize, size_t imtSize,
                                          size_t classSize)
{
    ASSERT(IsInitialized());
    if (vm_ == nullptr) {
        return nullptr;
    }
    // Create an instance of ark::Class. Since all objects in JS runtime must have a dynamic class
    // wrap the instance of ark::Class into an object with a dynamic class and mark ark::Class's
    // data as native data.
    //
    //                     +----------+
    //                     | JSHClass |
    //                     +----------+
    //                     | HClass   | <----+
    //                     +----------+      |
    //                                       |
    //                      Dynamic object   |
    //                     +--------------+  | classWord_
    //                 +-> | ObjetcHeader | -+
    // managed_object_ |   +--------------+
    //                 +-  | ark::Class | <- native data
    //                     +--------------+
    [[maybe_unused]] ecmascript::EcmaHandleScope scope(vm_->GetJSThread());
    ObjectFactory *factory = vm_->GetFactory();
    JSHandle<JSHClass> hclass = factory->NewEcmaDynClass(nullptr, JSHClass::SIZE, JSType::HCLASS, 0, 0);
    if (hclass.IsEmpty()) {
        return nullptr;
    }
    // We must cover the whole ark::Class by the native_field_mask
    ASSERT(classSize <= std::numeric_limits<uint64_t>::digits * sizeof(JSTaggedType));
    JSHClass *hclassPtr = reinterpret_cast<JSHClass *>(hclass.GetTaggedValue().GetTaggedObject());
    hclassPtr->SetObjectSize(ObjectHeader::ObjectHeaderSize() + classSize);
    hclassPtr->GetHClass()->SetNativeFieldMask(std::numeric_limits<uint64_t>::max());
    // All fields must be set before call SetClass to make TSAN happy.
    // The intstance of this HClass may be read by update remset thread.
    // The thread skips the object if this class is null.
    hclassPtr->SetClassWithoutBarrier(hclassPtr);

    JSHandle<TaggedObject> klassObj(vm_->GetJSThread(), factory->NewNonMovableDynObject(hclass));
    auto *klass =
        reinterpret_cast<Class *>(ToUintPtr(klassObj.GetObject<ObjectHeader>()) + ObjectHeader::ObjectHeaderSize());
    new (klass) Class(descriptor, panda_file::SourceLang::ECMASCRIPT, vtableSize, imtSize, classSize);
    klass->SetManagedObject(klassObj.GetObject<ObjectHeader>());
    AddCreatedClass(klass);
    return klass;
}

size_t EcmaClassLinkerExtension::GetClassSize([[maybe_unused]] ClassRoot root)
{
    ASSERT(IsInitialized());
    // Used only in test scenarios.
    return sizeof(Class);
}

size_t EcmaClassLinkerExtension::GetArrayClassSize()
{
    ASSERT(IsInitialized());

    return GetClassSize(ClassRoot::OBJECT);
}

void EcmaClassLinkerExtension::FreeClass([[maybe_unused]] Class *klass)
{
    ASSERT(IsInitialized());
    if (vm_ == nullptr) {
        return;
    }

    RemoveCreatedClass(klass);
}
}  // namespace ark::ecmascript
